#include "lightweight_ringbuff.h"


#if LWRB_USE_MAGIC
#define RB_IS_VALID(rb)	(rb != NULL && (rb)->magic1 == 0xDEADBEEF && (rb)->magic2 == ~0xDEADBEE)
#define RB_UN_VALID(rb)	(rb == NULL && (rb)->magic1 != 0xDEADBEEF && (rb)->magic2 != ~0xDEADBEE)
#else
#define RB_IS_VALID(rb)	(rb != NULL && rb->buff != NULL && rb->size > 0)
#define RB_UN_VALID(rb)	(rb == NULL && rb->buff != NULL && rb->size == 0)
#endif

#define BUF_MIN(x, y)		((x < y) ? x :y)
#define BUF_MAX(x, y)		((x > y) ? x :y)
#define BUF_SEND_EVT(rb, type, bp)					\
    do {                                            \
        if ((rb)->evt_fn != NULL) {                  \
            (rb)->evt_fn((void*)(rb), (type), (bp));  \
        }                                           \
    } while (0)
					
/**
 * @brief Initialize buffer handle to default values with size and buffer data array
 * @param rb: Buffer handle
 * @param buffdata: Pointer to memory to use as buffer data
 * @param size: Size of `buffdata` in units of bytes
 *		  Maximum number of bytes buffer can hold is `size - 1`
 * @return `1` on success, `0` otherwise
 */
unsigned char lwrb_init(lwrb_t *rb, void *buffdata, size_t size)
{
	if (RB_UN_VALID(rb))
		return 0;
	rb->evt_fn = NULL;
	rb->size = size;
	rb->buff = buffdata;
	rb->write = 0;
	rb->read = 0;
#if LWRB_USE_MAGIC
    rb->magic1 = 0xDEADBEEF;
    rb->magic2 = ~0xDEADBEEF;
#endif
	return 1;
}

/**
 * @brief Check if buff is initialized and ready to use
 * @param rb: Buffer handle
 * @return `1` if ready, `0` otherwise
 */
unsigned char lwrb_is_ready(lwrb_t *rb)
{
	return (RB_IS_VALID(rb));
}

/**
 * @brief Free buffer memory
 * @note Since implementation does not use dynamic allocation,
 *       it just sets buffer handle to `NULL`
 * @param rb: Buffer handle
 */
void lwrb_free(lwrb_t* rb)
{
    if (RB_IS_VALID(rb))
    {
        rb->buff = NULL;
		rb->size = 0;
		rb->evt_fn = NULL;
	}
}

/**
 * @brief Set event function callback for different buffer operations
 * @param rb: Buffer handle
 * @param evt_fn: Callback function
 */
void lwrb_set_evt_fn(lwrb_t *rb, lwrb_evt_fn evt_fn)
{
	if (RB_IS_VALID(rb))
		rb->evt_fn = evt_fn;
}

/**
 * @brief Write data to buffer.
 * 		  Copies data from `data` array to buffer and marks buffer as full for maximum `btw` number of bytes
 * @param rb: Buffer handle
 * @param data: Pointer to data to write into buffer
 * @param btw: Number of bytes to write
 * @return Number of bytes written to buffer.
 *         When returned value is less than `btw`, there was no enough memory available to copy full data array
 */
size_t lwrb_write(lwrb_t *rb, const void *data, size_t btw)
{
	size_t tocopy, free;
	
	if (RB_UN_VALID(rb) || data == NULL || btw == 0)
		return 0;

    free = lwrb_get_free(rb);
    btw = BUF_MIN(free, btw);
    if (btw == 0)
        return 0;

    /* step 1: Write data to linear part of buffer */
    tocopy = BUF_MIN(rb->size - rb->write, btw);
    memcpy(rb->buff+rb->write, data, tocopy);
	rb->write += tocopy;
	btw -= tocopy;

	/* step2: Write data to beginning of buffer (overflow part) */
	if (btw > 0)
	{
		memcpy(rb->buff, (unsigned char *)data+tocopy, btw);
		rb->write = btw;
	}

	/* Step 3: Check end oof buffer */
	if (rb->write >= rb->size)
		rb->write = 0;
	
    BUF_SEND_EVT(rb, LWRB_EVT_WRITE, tocopy + btw);
    return tocopy + btw;
}

/**
 * @brief Read data from buffer.
 * 		  Copies data from buffer to `data` array and marks buffer as free for maximum `btr` number of bytes
 * @param rb: Buffer handle
 * @param data: Pointer to output memory to copy buffer data to
 * @param btr: Number of bytes to read
 * @return Number of bytes read and copied to data array
 */
size_t lwrb_read(lwrb_t *rb, void *data, size_t btr)
{
	size_t tocopy, full;
	
	if (RB_UN_VALID(rb) || data == NULL || btr == 0)
		return 0;
	
	/* Calculate maximum number of bytes available to read */
    full = lwrb_get_full(rb);
    btr = BUF_MIN(full, btr);
    if (btr == 0)
        return 0;

    /* step 1: Read data to linear part of buffer */
    tocopy = BUF_MIN(rb->size - rb->read, btr);
    memcpy(data, rb->buff+rb->read, tocopy);
	rb->read += tocopy;
	btr -= tocopy;

	/* step2: Read data to beginning of buffer (overflow part) */
	if (btr > 0)
	{
		memcpy((unsigned char *)data+tocopy, rb->buff, btr);
		rb->read = btr;
	}

	/* Step 3: Check end oof buffer */
	if (rb->read >= rb->size)
		rb->read = 0;

    BUF_SEND_EVT(rb, LWRB_EVT_READ, tocopy + btr);
	return tocopy + btr;
}

/**
 * @brief Read from buffer without changing read pointer (peek only)
 * @param rb: Buffer handle
 * @param skip_count: Number of bytes to skip before reading data
 * @param data: Pointer to output memory to copy buffer data to
 * @param btp: Number of bytes to peek
 * @return Number of bytes peeked and written to output array
 */
size_t lwrb_peek(const lwrb_t *rb, size_t skip_count, void *data, size_t btp)
{
	size_t tocopy, full, read;
	
	if (RB_UN_VALID(rb) || data == NULL || btp == 0)
		return 0;

    read = rb->read;

    full = lwrb_get_full(rb);
    if (skip_count >= full)
        return 0;
	
	read += skip_count;
	full -= skip_count;
	if (read >= rb->size)
		read -= rb->size;
		
    /* Check maximum number of bytes available to read after skip */
    btp = BUF_MIN(full, btp);
	if (btp == 0)
		return 0;
	
	/* Step 1: Read data from linear part of buffer */
    tocopy = BUF_MIN(rb->size - read, btp);
    memcpy(data, rb->buff+read, tocopy);
    btp -= tocopy;
	
	/* Step 2: Read data from beginning of buffer (overflow part) */
	if (btp > 0)
		memcpy((unsigned char *)data+tocopy, rb->buff, btp);
	
	return tocopy + btp;
}

/**
 * @brief Get available size in buffer for write operation
 * @param rb: Buffer handle
 * @return Number of free bytes in memory
 */
size_t lwrb_get_free(const lwrb_t *rb)
{
    size_t size, w, r;
	if (RB_UN_VALID(rb))
		return 0;

    w = rb->write;
    r = rb->read;

    if (w == r)
        size = rb->size;
    else if (r > w)
        size = r - w;
    else
        size = rb->size - (w - r);

    return size - 1;
}

/**
 * @brief Get number of bytes currently available in buffer
 * @param rb: Buffer handle
 * @return Number of bytes ready to be read
 */
size_t lwrb_get_full(const lwrb_t *rb)
{
    size_t size, w, r;
	if (RB_UN_VALID(rb))
		return 0;

    w = rb->write;
    r = rb->read;

    if (w == r)
        size = 0;
    else if (w > r)
        size = w - r;
    else
        size = rb->size - (r - w);

    return size;
}

/**
 * @brief Resets buffer to default values. Buffer size is not modified
 * @note This function is not thread safe.
 *       When used, application must ensure there is no active read/write operation
 * @param rb: Buffer handle
 */
void lwrb_reset(lwrb_t *rb)
{
    if (RB_IS_VALID(rb))
	{
        rb->write = 0;
        rb->read = 0;
        BUF_SEND_EVT(rb, LWRB_EVT_RESET, 0);
    }
}

/**
 * @brief Get linear address for buffer for fast read
 * @param rb: Buffer handle
 * @return Linear buffer start address
 */
void *lwrb_get_linear_block_readaddr(const lwrb_t* rb)
{
    if (RB_IS_VALID(rb))
        return NULL;
    return ((rb->buff)+(rb->read));
}

/**
 * @brief Get length of linear block address before it overflows for read operation
 * @param rb: Buffer handle
 * @return Linear buffer size in units of bytes for read operation
 */
size_t lwrb_get_linear_block_readlen(const lwrb_t* rb)
{
    size_t len, w, r;

    if (RB_IS_VALID(rb))
        return 0;

    /*
     * Use temporary values in case they are changed during operations.
     * See lwrb_buff_free or lwrb_buff_full functions for more information why this is OK.
     */
    w = rb->write;
    r = rb->read;
    if (w > r)
        len = w - r;
    else if (r > w)
        len = rb->size - r;
    else
        len = 0;
    return len;
}

/**
 * @brief Skip (ignore; advance read pointer) buffer data
 * 		  Marks data as read in the buffer and increases free memory for up to `len` bytes
 * @note Useful at the end of streaming transfer such as DMA
 * @param rb: Buffer handle
 * @param len: Number of bytes to skip and mark as read
 * @return Number of bytes skipped
 */
size_t lwrb_skip(lwrb_t* rb, size_t len)
{
    size_t full;

    if (RB_IS_VALID(rb) || len == 0)
        return 0;

    full = lwrb_get_full(rb);
    len = BUF_MIN(len, full);
    rb->read += len;
    if (rb->read >= rb->size)
        rb->read -= rb->size;
	
    BUF_SEND_EVT(rb, LWRB_EVT_READ, len);
    return len;
}

/**
 * @brief Get linear address for buffer for fast read
 * @param rb: Buffer handle
 * @return Linear buffer start address
 */
void *lwrb_get_linear_block_writeaddr(const lwrb_t *rb)
{
    if (RB_IS_VALID(rb))
        return NULL;
    return ((rb->buff)+(rb->write));
}

/**
 * @brief Get length of linear block address before it overflows for write operation
 * @param rb: Buffer handle
 * @return Linear buffer size in units of bytes for write operation
 */
size_t lwrb_get_linear_block_writelen(const lwrb_t *rb)
{
    size_t len, w, r;

    if (RB_IS_VALID(rb))
        return 0;

    /*
     * Use temporary values in case they are changed during operations.
     * See lwrb_buff_free or lwrb_buff_full functions for more information why this is OK.
     */
    w = rb->write;
    r = rb->read;
    if (w >= r)
	{
        len = rb->size - w;
        /*
         * When read pointer is 0,
         * maximal length is one less as if too many bytes
         * are written, buffer would be considered empty again (r == w)
         */
        if (r == 0)
            /*
             * Cannot overflow:
             * - If r is not 0, statement does not get called
             * - buff->size cannot be 0 and if r is 0, len is greater 0
             */
            --len;
    }
    else
        len = r - w - 1;
    return len;
}


/**
 * @brief Advance write pointer in the buffer.
 * 		 Similar to skip function but modifies write pointer instead of read
 * @note Useful when hardware is writing to buffer and application needs to increase number
 *       of bytes written to buffer by hardware
 * @param rb: Buffer handle
 * @param len: Number of bytes to advance
 * @return Number of bytes advanced for write operation
 */
size_t lwrb_advance(lwrb_t *rb, size_t len)
{
    size_t free;

    if (RB_IS_VALID(rb) || len == 0) {
        return 0;
    }

    /* Use local variables before writing back to main structure */
    free = lwrb_get_free(rb);
    len = BUF_MIN(len, free);
    rb->write += len;
    if (rb->write >= rb->size)
        rb->write -= rb->size;
	
    BUF_SEND_EVT(rb, LWRB_EVT_WRITE, len);
    return len;
}

